package com.demo.security.token;

import com.demo.common.constants.CommonConstants;
import com.demo.security.exceptions.JwtTokenAuthenticationException;
import com.demo.security.model.JwtIssuerIdentify;
import com.demo.security.properties.AuthenticationProperties;
import com.demo.security.token.authenticated.AuthenticatedToken;
import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;

/**
 * JWT创建工厂
 * 根据用户的认证信息,生成可访问的ACCESS_TOKEN
 * @author molong
 * @since 2024/5/23
 */
@Component
public class JwtTokenFactory {

    private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenFactory.class);

    private final AuthenticationProperties settings;

    public JwtTokenFactory(AuthenticationProperties settings) {
        this.settings = settings;
    }

    /**
     * 根据认证信息创建一个REFRESH_TOKEN
     *
     * @param authentication 登录成功后的用户认证信息
     * @return REFRESH_TOKEN
     */
    public JwtAccessToken createRefreshToken(Authentication authentication){
        Claims claims = Jwts.claims();
        String token = createToken(claims, authentication,
                Collections.singletonList(CommonConstants.FRESH_TOKEN),
                settings.getJwt().getRefreshTokenExpTime());
        LOGGER.debug("Create a fresh token {} for {} ", token, authentication.getPrincipal());
        return new JwtAccessToken(token, claims);
    }

    /**
     * 根据认证信息创建一个ACCESS_TOKEN
     *
     * @param authentication 登录成功后的用户认证信息
     * @return ACCESS_TOKEN
     */

    public JwtAccessToken createAccessJwtToken(Authentication authentication) {
        Claims claims = Jwts.claims();
        String token = createToken(claims,
                authentication,
                Collections.singletonList(CommonConstants.ACCESS_TOKEN),
                settings.getJwt().getTokenExpirationTime());
        LOGGER.debug("Create an access token {} for {} ", token, authentication.getPrincipal());
        return new JwtAccessToken(token, claims);
    }

    /**
     * 验证TOKEN的有效性,如果TOKEN处于有效期内,则并通过TOKEN获取该TOKEN所拥有的用户信息.
     * 并将用户名设置到TOKEN实体内,并可以通过{@link Authentication#getPrincipal()}获取到用户名
     *
     * @param authentication 包含一个REFRESH_TOKEN的实体TOKEN
     * @return 验证成功后的TOKEN
     * @throws JwtTokenAuthenticationException TOKEN无效
     */
    public Authentication validate(Authentication authentication) {
        Token refreshToken = (Token) authentication.getPrincipal();
        Jws<Claims> jws = validateTokenClaims(refreshToken);
        String user = jws.getBody().getAudience();
        Date date = jws.getBody().getExpiration();
        return new AuthenticatedToken(user, date);
    }

    /**
     * 创建TOKEN,设置TOKEN相关PayLoad信息
     *
     * @param claims    JWT 载荷信息
     * @param validTime 有效时间
     * @return JWT 令牌
     */
    private String createToken(Claims claims, Authentication authentication, List<String> scope, Long validTime) {
        LocalDateTime currentTime = LocalDateTime.now();
        claims.setId(UUID.randomUUID().toString())
                //sub: 该JWT所面向的用户
                .setSubject(authentication.getName())
                //aud: 接收该JWT的一方
                .setAudience(authentication.getName())
                //iat(issued at): 在什么时候签发的(UNIX时间)
                .setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant()))
                //aud: 签发者
                .setIssuer(JwtIssuerIdentify.INSTANCE.getSign())
                //exp(expires): 什么时候过期
                .setExpiration(Date.from(currentTime.plusSeconds(validTime).atZone(ZoneId.systemDefault()).toInstant()));
        claims.put("scope", scope);
        return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, settings.getJwt().getTokenSigningKey()).compact();
    }

    /**
     * 验证解析有效的token签名
     *
     * @throws JwtTokenAuthenticationException  无效token
     */
    private Jws<Claims> validateTokenClaims(Token signingKey) {
        try {
            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(settings.getJwt().getTokenSigningKey()).parseClaimsJws(signingKey.getToken());
            Assert.isTrue(claimsJws.getBody().getIssuer().equals(JwtIssuerIdentify.INSTANCE.getSign()), "Unique sign is invalid");
            return claimsJws;
        } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
            LOGGER.debug("Invalid JWT Token:{}", signingKey.getToken());
            throw new JwtTokenAuthenticationException("Invalid token", ex);
        } catch (ExpiredJwtException expiredEx) {
            LOGGER.debug("Token is expired {}", signingKey.getToken());
            throw new JwtTokenAuthenticationException("Token expired", expiredEx);
        }
    }
}
